<?php

function    log_trace( $message, $file=NULL, $line=0 )
{
    if ( !empty($file) ) {
        fwrite(STDERR, sprintf("%s:%s ", $file, $line));
    }
    fwrite(STDERR, $message);
    if ( '\n' != $messsage[strlen($message) - 1] ) {
        fwrite(STDERR, "\n");
    }
}

class   UdperException  extends Exception
{
    public function __construct( $message, $code = 0 )
    {
        parent::__construct( $message, $code );
    }
}

class   Udper
{
    private $__ipaddress = '127.0.0.1';
    private $__port = 4344;
    private $__instance = NULL;

    public  function    __construct($ipaddress=NULL, $port=0)
    {
        if (extension_loaded('sockets')) {
            // using more efficient sockets extension
            $this->__instance = new SocketsUdper($ipaddress, $port);
        }
        else {
            // using embedded fsock
            $this->__instance = new FSocketUdper($ipaddress, $port);
        }

        if ( !empty($ipaddress) ) {
            $this->__ipaddress = $ipaddress;
        }
        if ( !empty($port) ) {
            $this->__port = $port;
        }
    }

    public  function    __destruct()
    {
    }

    /**
     * listening
     *   if $ipaddress is empty, listening on ipaddress provided when instance class
     */
    public  function    listen($ipaddress=NULL, $port=0)
    {
        return  $this->__instance->listen($ipaddress, $port);
    }

    public  function    send($data, $timeout=0, $ipaddress=NULL, $port=0)
    {
        return  $this->__instance->send($data, $timeout, $ipaddress, $port);
    }

    public  function    recv($timeout=-1, &$ipaddress, &$port)
    {
        return  $this->__instance->recv($timeout, $ipaddress, $port);
    }
}

?>
<?php

class   SocketsUdper
{
    private $__ipaddress = '127.0.0.1';
    private $__port = 4344;
    private $__socket = NULL;

    public  function    __construct( $ipaddress=NULL, $port=0 )
    {
        if ( !empty($ipaddress) ) {
            $this->__ipaddress = $ipaddress;
        }
        if ( !empty($port) ) {
            $this->__port = $port;
        }

        $this->__createSocket();
    }

    public  function    listen($ipaddress=NULL, $port=0)
    {
        if ( empty($this->__socket) ) {
            $this->__createSocket();
        }

        if ( empty($port) ) {
            $port = $this->__port;
        }
        //$ret = socket_bind($this->__socket, '0.0.0.0', intval($port));
        $ret = socket_bind($this->__socket, '0.0.0.0', $port);
        if ( false === $ret ) {
            throw new UdperException( sprintf("fail to bind UDP socket on port %s : %s", $port, socket_strerror(socket_last_error($this->__socket))) );
        }

        //$ret = socket_listen($this->__socket);
        //if ( false === $ret ) {
        //    throw new UdperException( sprintf("fail to listen UDP socket on port %s : %s", $port, socket_strerror(socket_last_error($this->__socket))) );
        //}

        return  true;
    }

    public  function    send($data, $timeout=0, $ipaddress=NULL, $port=0)
    {
        if ( empty($this->__socket) ) {
            $this->__createSocket();
        }

        if ( empty($ipaddress) ) {
            $ipaddress = $this->__ipaddress;
            $port = $this->__port;
        }

        log_trace( sprintf("sock SEND   TO '%s:%s' DATA:'%s'", $ipaddress, $port, $data) );
        $ret = socket_sendto($this->__socket, $data, strlen($data), 0, $ipaddress, $port);
        if ( false === $ret ) {
            throw new UdperException( sprintf("fail to send UDP packet to %s:%s : %s", $ipaddress, $port, socket_strerror(socket_last_error($this->__socket))) );
        }

        return  true;
    }

    public  function    recv($timeout=0, &$ipaddress=NULL, &$port=0)
    {
        if ( empty($this->__socket) ) {
            $this->__createSocket();
        }

        if ( $timeout < 0 ) {
            $timeout = 86400;
        }
        for( ;$timeout >= 0; $timeout-- ) {
            $read_set = array();
            $read_set[] = $this->__socket;
            array_push($read_set, $this->__socket);
            $write_set = array();
            $except_set = array();
            log_trace( sprintf("sock WAIT read_set:'%s'", print_r($read_set, true)) );
            $ret = socket_select($read_set, $write_set, $except_set, 1);
            log_trace( sprintf("sock WAIT read_set:'%s' ret:'%s'", print_r($read_set, true), $ret) );
            if ( $ret > 0 ) {
                break;;
            }
        }
        if ( $timeout < 1 ) {
            return  false;
        }

        $ret = socket_recvfrom($this->__socket, $data, 65536, 0, $ipaddress, $port);
        if ( $ret < 0 ) {
            throw new UdperException( sprintf("fail to receive UDP packet : %s", socket_strerror(socket_last_error())) );
        }
        log_trace( sprintf("sock RECV FROM '%s:%s' DATA:'%s'", $ipaddress, $port, $data) );

        return  $data;
    }

    ///////////////////////////////////////////////////////////////////

    private function    __createSocket()
    {
        $this->__socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
        if ( false === $this->__socket ) {
            throw new UdperException( sprintf("fail to create UDP socket : %s", socket_strerror(socket_last_error())) );
        }
        $ret = socket_set_nonblock($this->__socket);
        if ( false === $ret ) {
            throw new UdperException( sprintf("fail to set non-block UDP socket : %s", socket_strerror(socket_last_error($this->__socket))) );
        }
        //$ret = socket_set_option($this->__socket, IPPROTO_TCP, TCP_NODELAY, 1);
        //$ret = socket_set_option($this->__socket, IPPROTO_UDP, TCP_NODELAY, 1);
        //if ( false === $ret ) {
        //    throw new UdperException( sprintf("fail to set UDP socket option TCP_NODELAY : %s", socket_strerror(socket_last_error($this->__socket))) );
        //}
        $ret = socket_set_option($this->__socket, SOL_SOCKET, SO_RCVTIMEO, array("sec"=>10, "usec"=>0));
        if ( false === $ret ) {
            throw new UdperException( sprintf("fail to set UDP socket option SO_RCVTIMEO : %s", socket_strerror(socket_last_error($this->__socket))) );
        }
        $ret = socket_set_option($this->__socket, SOL_SOCKET, SO_SNDTIMEO, array("sec"=>3, "usec"=>0));
        if ( false === $ret ) {
            throw new UdperException( sprintf("fail to set UDP socket option SO_SNDRCVTIMEO : %s", socket_strerror(socket_last_error($this->__socket))) );
        }
        $ret = socket_set_option($this->__socket, SOL_SOCKET, SO_REUSEADDR, 1);
        if ( false === $ret ) {
            throw new UdperException( sprintf("fail to set UDP socket option SO_REUSEADDR : %s", socket_strerror(socket_last_error($this->__socket))) );
        }
        for ($i=10;$i<250; $i+=10 ) {
            echo $i,"\n";
            $ret = socket_set_option($this->__socket, SOL_SOCKET, SO_RCVBUF, 1024 * i);
            if ( false === $ret ) {
                break;
            }
        }
        for ($i=10;$i<250; $i+=10 ) {
            echo $i,"\n";
            $ret = socket_set_option($this->__socket, SOL_SOCKET, SO_SNDBUF, 1024 * i);
            if ( false === $ret ) {
                break;
            }
        }

        return  true;
    }
}

?>
<?php

define( 'TIMEOUT_DEFAULT', 10 );

class   FSocketUdper
{
    private $__ipaddress = '127.0.0.1';
    private $__port = 4344;
    private $__socket_server = NULL;
    private $__socket_client = NULL;


    public  function    __construct( $ipaddress=NULL, $port=0 )
    {
        if ( !empty($ipaddress) ) {
            $this->__ipaddress = $ipaddress;
        }
        if ( !empty($port) ) {
            $this->__port = $port;
        }

    }

    public  function    listen($ipaddress=NULL, $port=0)
    {
        if ( empty($ipaddress) ) {
            $socket_url = $this->__getSocketUrl($this->__ipaddress, $this->__port);
        }
        else {
            $socket_url = $this->__getSocketUrl($ipaddress, $port);
        }

        if( $this->__socket_server ) {
            fclose($this->__socket_server);
            $this->__socket_server = NULL;
        }
        $this->__socket_server = stream_socket_server($socket_url, $errno, $errstr, STREAM_SERVER_BIND);
        if ( !$this->__socket_server ) {
            throw new UdperException(sprintf("Fail to create stream_socket_server '%s' : [%d] %s", $socket_url, $errno, $errstr));
        }

        return  true;
    }

    public  function    send($data, $timeout=0, $ipaddress=NULL, $port=0)
    {
        if ( empty($ipaddress) ) {
            //$socket_url = $this->__getSocketUrl($this->__ipaddress, $this->__port);
            $peer = sprintf("%s:%s", $this->__ipaddress, $this->__port);
        }
        else {
            //$socket_url = $this->__getSocketUrl($ipaddress, $port);
            $peer = sprintf("%s:%s", $ipaddress, $port);
        }

        $socket = $this->__getSocket();
        log_trace( sprintf("fsock SEND   TO:'%s' DATA:'%s'", $peer, $data) );
        stream_socket_sendto($socket, $data, 0, $peer);

        return  true;
    }

    public  function    recv($timeout=0, &$ipaddress=NULL, &$port=0)
    {
        $socket = $this->__getSocket();
        if ( $timeout < 0 ) {
            $timeout = 86400;
        }
        $read_set = array();
        for( ;$timeout >= 0; $timeout-- ) {
            $read_set[] = $socket;
            $write_set = NULL;
            $except_set = NULL;
            $ret = stream_select($read_set, $write_set, $except_set, 1);
            if ( $ret > 0 ) {
                break;;
            }
        }
        if ( $timeout < 1 ) {
            return  false;
        }

        $data = stream_socket_recvfrom($socket, 65536, 0, $peer);
        log_trace( sprintf("fsock RECV FROM:'%s' DATA:'%s'", $peer, $data) );
        list($ipaddress, $port) = split( ':', $peer);

        return  $data;
    }

    ///////////////////////////////////////////////////////////////////

    private  function    __getSocketUrl($ipaddress=NULL, $port=0)
    {
        if ('/' == $ipaddress[0]) {
            $socket_url = sprintf("unix://%s", $ipaddress);
        }
        elseif (!empty($ipaddress)) {
            $socket_url = sprintf("udp://%s:%s", $ipaddress, $port);
        }
        else {
            throw new UdperException(sprintf("Invalid IP/address (%s:%s).", $ipaddress, $port));
        }

        return  $socket_url;
    }

    private  function    __getSocket()
    {
        if ( empty($this->__socket_server) && empty($this->__socket_client) ) {
            $socket_url = $this->__getSocketUrl($this->__ipaddress, $this->__port);
            $this->__socket_client = stream_socket_client($socket_url, $errno, $errstr);
            if ( !$this->__socket_client ) {
                throw new UdperException(sprintf("Fail to create stream_socket_client '%s' : [%d] %s", $socket_url, $errno, $errstr));
            }

            stream_set_timeout($this->__socket_client, TIMEOUT_DEFAULT);
            $socket = $this->__socket_client;
        }
        elseif ( !empty($this->__socket_server) ) {
            $socket = $this->__socket_server;
        }
        else {
            $socket = $this->__socket_client;
        }

        return  $socket;
    }

}

?>

